﻿#if UNITY_EDITOR
using Devil;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit
{
    [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public class ProjectConfigAttribute : System.Attribute
    {
        public string Title { get; set; }
        public string Keyword { get; set; }

        public ProjectConfigAttribute()
        {

        }

        public ProjectConfigAttribute(string title)
        {
            this.Title = title;
        }
    }

    public class ProjectConfiguration : ScriptableObject, ISerializationCallbackReceiver
    {
        readonly static System.Type[] NONE_ARG = new System.Type[0];

        #region initializer
        [SettingsProviderGroup]
        static SettingsProvider[] GetProjectSettings()
        {
            var rex = new Regex("([a-z][A-Z]|_|[A-Z]{2,}[a-z])");
            MatchEvaluator ev = (match) => match.Length == 1 ? " " : match.Value.Insert(match.Length == 2 ? 1 : (match.Length - 2), " ");

            var cfg = GetCustomProjectSettings(true);
            var len = cfg.m_AllConfigs == null ? 0 : cfg.m_AllConfigs.Length;
            var settings = new SettingsProvider[len];
            for (int i = 0; i < len; i++)
            {
                var set = cfg.m_AllConfigs[i];
                if (set == null)
                {
                    settings[i] = new SettingsProvider("Unknown" + i, SettingsScope.Project);
                    continue;
                }
                var att = set.GetType().GetCustomAttribute<ProjectConfigAttribute>();
                var title = cfg.m_AllConfigs[i].GetTitle(att);
                title = rex.Replace(title, ev);
                settings[i] = new Setting(set, title, att == null || string.IsNullOrEmpty(att.Keyword) ? null : new string[] { att.Keyword });
            }
            return settings;
        }

        static bool IsConfigurableType(System.Type type)
        {
            return !type.IsAbstract && GlobalUtil.IsTypeInheritedFrom(type, typeof(Configurable), false)
                && type.GetCustomAttribute<System.SerializableAttribute>() != null
                && type.GetConstructor(NONE_ARG) != null;
        }

        static ProjectConfiguration mCfg;

        static ProjectConfiguration GetCustomProjectSettings(bool alwaysLoad)
        {
            //InternalEditorUtility.LoadSerializedFileAndForget("")
            if (ParallelDispatcher.IsMainThread)
            {
                mCfg = AssetDatabase.LoadAssetAtPath<ProjectConfiguration>("Assets/CustomProjectSettings.asset");
                if (mCfg == null)
                {
                    mCfg = ScriptableObject.CreateInstance<ProjectConfiguration>();
                    mCfg.name = "CustomProjectSettings";
                    mCfg.LoadAllConfigs(false);
                    AssetDatabase.CreateAsset(mCfg, "Assets/CustomProjectSettings.asset");
                }
                else if (alwaysLoad)
                {
                    mCfg.LoadAllConfigs(true);
                }
            }
            return mCfg;
        }

        public static T GetConfigurable<T>() where T : Configurable, new()
        {
            var cfg = GetCustomProjectSettings(false);
            var len = cfg.m_AllConfigs == null ? 0 : cfg.m_AllConfigs.Length;
            for (int i = 0; i < len; i++)
            {
                var set = cfg.m_AllConfigs[i];
                if (set is T)
                    return (T)set;
            }
            return new T();
        }

        [InitializeOnLoadMethodParallel(IsOnMainThread = true, ExecutionOrder = InitializeOrder.First - 1)]
        static void Initialize()
        {
            mCfg = AssetDatabase.LoadAssetAtPath<ProjectConfiguration>("Assets/CustomProjectSettings.asset");
            if (mCfg != null)
            {
                mCfg.LoadAllConfigs(true);
            }
        }

        #endregion


        [SerializeReference]
        Configurable[] m_AllConfigs;
        
        public int Count { get { return m_AllConfigs == null ? 0 : m_AllConfigs.Length; } }

        public Configurable GetConfigAt(int index)
        {
            var cfg = m_AllConfigs[index];
            if (cfg != null)
                cfg._cfg = this;
            return cfg;
        }

        [ContextMenu("Get All Configs")]
        void ForceReload()
        {
            LoadAllConfigs(true);
        }

        void LoadAllConfigs(bool markDirty)
        {
            //this.hideFlags |= HideFlags.NotEditable;
            this.hideFlags &= ~(HideFlags.HideInInspector | HideFlags.NotEditable);
            var cfgs = new List<Configurable>();
            HashSet<System.Type> exists = new HashSet<System.Type>();
            var dirty = false;
            if (m_AllConfigs != null)
            {
                for (int i = 0; i < m_AllConfigs.Length; i++)
                {
                    var cfg = m_AllConfigs[i];
                    if (cfg != null)
                    {
                        cfg._cfg = this;
                        //cfg.m_Name = cfg.GetType().Name;
                        cfgs.Add(cfg);
                        exists.Add(cfg.GetType());
                    }
                    else
                    {
                        dirty = true;
                    }
                }
            }
            //var size = cfgs.Count;
            var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetTypes();
                foreach (var type in types)
                {
                    if (exists.Contains(type) || !IsConfigurableType(type))
                        continue;
                    try
                    {
                        var constructor = type.GetConstructor(NONE_ARG);
                        if (constructor == null)
                            continue;
                        var cfg = constructor.Invoke(new object[0]) as Configurable;
                        if (cfg != null)
                        {
                            dirty = true;
                            //cfg.m_Name = cfg.GetType().Name;
                            cfg._cfg = this;
                            cfgs.Add(cfg);
                        }
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e);
                    }
                }
            }
            for (int i = 0; i < cfgs.Count; i++)
            {
                var cfg = cfgs[i];
                var mtd = cfg.GetType().GetMethod("OnGUI", BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public);
                cfg._overrideGUI = mtd != null;
                cfg._cfg = this;
            }
            if (dirty)
            {
                m_AllConfigs = cfgs.ToArray();
                if (markDirty)
                    EditorUtility.SetDirty(this);
            }
        }

        public void OnBeforeSerialize()
        {
            if (m_AllConfigs != null)
            {
                for (int i = 0; i < m_AllConfigs.Length; i++)
                {
                    var cfg = m_AllConfigs[i];
                    if (cfg != null)
                    {
                        cfg._cfg = this;
                    }
                }
            }
        }

        public void OnAfterDeserialize()
        {
            if (m_AllConfigs != null)
            {
                for (int i = 0; i < m_AllConfigs.Length; i++)
                {
                    var cfg = m_AllConfigs[i];
                    if (cfg != null)
                        cfg._cfg = this;
                }
            }
        }

        public int IndexOfConfig(Configurable cfg)
        {
            if (m_AllConfigs == null)
                return -1;
            for (int i = 0; i < m_AllConfigs.Length; i++)
            {
                if (cfg == m_AllConfigs[i])
                    return i;
            }
            return -1;
        }

        class Setting : SettingsProvider
        {
            Configurable mSetting;
            VisualElement mVisual;
            Vector2 mScroll;

            public Setting(Configurable set, string title, IEnumerable<string> keywords): base(title, SettingsScope.Project, keywords)
            {
                mSetting = set;
            }

            public override void OnActivate(string searchContext, VisualElement rootElement)
            {
                base.OnActivate(searchContext, rootElement);
                mVisual = rootElement;
                if (mSetting != null)
                {
                    mSetting.OnEnableGUI();
                }
            }

            public override void OnDeactivate()
            {
                base.OnDeactivate();
                if(mSetting != null)
                {
                    mSetting.OnDisableGUI();
                }
            }

            public override void OnGUI(string searchContext)
            {
                //base.OnGUI(searchContext);
                //EditorGUILayout.Space();
                var rect = mVisual.contentRect;
                EditorGUI.indentLevel = 0;
                var content = new Rect(rect.x + 10, rect.y + 10, rect.width - 15, rect.height - 40);
                GUILayout.BeginArea(content);
                mScroll = EditorGUILayout.BeginScrollView(mScroll);
                if (mSetting != null)
                {
                    EditorGUIUtility.labelWidth = 250f;
                    mSetting.OnGUI();
                }
                EditorGUILayout.EndScrollView();
                GUILayout.EndArea();
            }
        }


        [System.Serializable]
        public abstract class Configurable
        {
            //internal readonly bool _overrideGui;
            //[SerializeField]
            //internal string m_Name;
            internal ProjectConfiguration _cfg;
            internal bool _overrideGUI;
            SerializedObject mSerObj;
            SerializedProperty mSerProp;
            int mSerIndex = -1;
            bool mInitGUI;

            public void SetDirty()
            {
                if (_cfg != null)
                    EditorUtility.SetDirty(_cfg);
            }

            internal string GetTitle(ProjectConfigAttribute att)
            {
                if (att != null && !string.IsNullOrEmpty(att.Title))
                    return att.Title;
                var title = ParallelUtils.Concat("Game Toolkit/", GetType().Name);
                return title;
            }

            public SerializedObject serializedObject { get { InitSerializedObj(); return mSerObj; } }
            public SerializedProperty serializedSettings
            {
                get
                {
                    InitSerializedObj();
                    if (mSerObj == null || mSerIndex == -1)
                        return null;
                    return mSerProp.GetArrayElementAtIndex(mSerIndex);
                }
            }

            public SerializedProperty FindPropertyRelative(string path)
            {
                InitSerializedObj();
                if(mSerProp != null && mSerIndex != -1)
                {
                    var prop = mSerProp.GetArrayElementAtIndex(mSerIndex);
                    return prop.FindPropertyRelative(path);
                }
                else
                {
                    return null;
                }
            }

            public Configurable()
            {
                //m_Name = GetType().Name;
                var mtd = GetType().GetMethod("OnGUI", BindingFlags.Instance | BindingFlags.DeclaredOnly);
                _overrideGUI = mtd != null;
            }

            void InitSerializedObj()
            {
                if (!mInitGUI)
                {
                    mInitGUI = true;
                    mSerIndex = _cfg.IndexOfConfig(this);
                    if (mSerIndex != -1)
                    {
                        mSerObj = new SerializedObject(_cfg);
                        mSerProp = mSerObj.FindProperty("m_AllConfigs");
                    }
                }
            }

            public virtual void OnEnableGUI()
            {
                //mSerIndex = _cfg.IndexOfConfig(this);
                //if (mSerIndex != -1)
                //{
                //    mSerObj = new SerializedObject(_cfg);
                //    mSerProp = mSerObj.FindProperty("m_AllConfigs");
                //}
            }

            public virtual void OnDisableGUI()
            {
                mSerIndex = -1;
                mInitGUI = false;
                if (mSerObj != null)
                {
                    mSerObj.Dispose();
                    mSerObj = null;
                    mSerProp = null;
                }
            }

            public virtual void OnGUI()
            {
                InitSerializedObj();
                if (mSerObj != null)
                {
                    mSerObj.Update();
                    var property = mSerProp.GetArrayElementAtIndex(mSerIndex);
                    if (property == null)
                        return;
                    var depth = property.depth;
                    if (property.NextVisible(true) && property.depth > depth)
                    {
                        EditorGUI.BeginChangeCheck();
                        do
                        {
                            EditorGUILayout.PropertyField(property, true);
                        } while (property.NextVisible(false) && property.depth > depth);
                        var change = EditorGUI.EndChangeCheck();
                        if (change)
                            mSerObj.ApplyModifiedProperties();
                    }
                }
            }

        }
    }
}
#endif